using System;
using System.Collections.Generic;
using Habanero.Base;
using Habanero.BO;
using Habanero.BO.ClassDefinition;
using Habanero.BO.Loaders;
using Habanero.DB;
using UseCaseManager.BO;

namespace UseCaseManager.Test.BO
{

    /// <summary>
    /// Provides a set of utilities to be used by tests for BusinessObjects in this
    /// application.  The default utilities to create sample BusinessObjects are located
    /// at the top of the Def file for each BO test.
    ///
    /// You can safely add additional utilities to this particular file since it is
    /// only written once, whereas the Def classes are overwritten with each generation.
    /// </summary>
    public class TestUtils
    {
        private static Random _randomGenerator;

        protected static Random Random
        {
            get
            {
                if (_randomGenerator == null) _randomGenerator = new Random();
                return _randomGenerator;
            }
        }

        public static int GetRandomInt()
        {
            return GetRandomInt(100000);
        }

        public static int GetRandomInt(int max)
        {
            return Random.Next(0, max);
        }

        public static int GetRandomInt(int min, int max)
        {
            return Random.Next(min, max);
        }

        public static string GetRandomString()
        {
            return GetRandomInt().ToString();
        }

        public static string GetRandomString(int maxLength)
        {
            if (maxLength > 9) maxLength = 9;
            int max = Convert.ToInt32(Math.Pow(10, maxLength)) - 1;
            return GetRandomInt(max).ToString();
        }

        public static string GetRandomString(int minLength, int maxLength)
        {
            if (minLength > 9) minLength = 9;
            int min = Convert.ToInt32(Math.Pow(10, minLength)) - 1;
            if (maxLength > 9) maxLength = 9;
            int max = Convert.ToInt32(Math.Pow(10, maxLength)) - 1;
            return GetRandomInt(min, max).ToString();
        }

        public static bool GetRandomBoolean()
        {
            return (GetRandomInt(100000) > 50000);
        }

        public static DateTime GetRandomDate()
        {
            return DateTime.Now;
        }

        public static DateTime GetRandomDate(string max)
        {
            string start = DateTime.MinValue.ToString("yyyy/MM/dd");
            return GetRandomDate(start, max);
        }

        public static DateTime GetRandomDate(string min, string max)
        {
            DateTime start = DateTime.Parse(min);

            int range = (DateTime.Parse(max) - start).Days;
            return start.AddDays(GetRandomInt(range));
        }

        /// <summary>
        /// Takes a lookup list generated by Habanero and randomly selects a value
        /// from the list
        /// </summary>
        public static object GetRandomLookupListValue(Dictionary<string, object> lookupList)
        {
            object[] values = new object[lookupList.Count];
            lookupList.Values.CopyTo(values, 0);
            return values[GetRandomInt(0, values.Length - 1)];
        }

        /// <summary>
        /// Waits for the garbage collector to clear dereferenced objects in order
        /// to ensure accurate testing
        /// </summary>
        public static void WaitForGC()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        /// <summary>
        /// Clears all the database tables holding business object data.
        /// WARNING: This is an irreversible action, be sure to use a test database.
        /// </summary>
        public static void ClearAllTables()
        {
            if (BORegistry.DataAccessor is DataAccessorInMemory)
            {
                BORegistry.DataAccessor = new DataAccessorInMemory();
            }
            else
            {
                //-----------------------------------------------------------------------
                // Deletes the contents of each of the tables.
                // Orders the tables to prevent parents being deleted before their
                // children, by assuming that the object on the 1 side of 1-to-many
                // is the parent.
                // Replace these lines below if this does not work for your setup.
                // Table names may need delimiters - adjust as needed for your database.
                //-----------------------------------------------------------------------
                foreach (ClassDef classDef in GetClassDefsOrderedForDeletion())
                {
                    DatabaseConnection.CurrentConnection.ExecuteRawSql("Delete from " + classDef.TableName);
                }
            }
        }

        /// <summary>
        /// Gets a list of classdefs ordered for deletion in order to avoid
        /// foreign key conflicts where parents are deleted before their children.
        /// </summary>
        public static IList<ClassDef> GetClassDefsOrderedForDeletion()
        {
            IList<ClassDef> classDefs = new List<ClassDef>();
            foreach (ClassDef classDef in ClassDef.ClassDefs)
            {
                classDefs.Add(classDef);
            }

            bool deleteOrderCorrect;
            do
            {
                deleteOrderCorrect = true;
                for (int position = 0; position < classDefs.Count; position++)
                {
                    ClassDef classDef = classDefs[position];
                    for (int parentPosition = 0; parentPosition < position; parentPosition++)
                    {
                        ClassDef potentialParentClassDef = classDefs[parentPosition];
                        if (ClassMustBeDeletedBeforeParent(classDef, potentialParentClassDef))
                        {
                            classDefs.Remove(classDef);
                            classDefs.Insert(parentPosition, classDef);
                            deleteOrderCorrect = false;
                            break;
                        }
                    }
                    if (!deleteOrderCorrect) break;
                }
            } while (!deleteOrderCorrect);

            return classDefs;
        }

        /// <summary>
        /// Indicates if the given class needs to be deleted before the one provided,
        /// which occurs when the parent is on the 1 side of 1-to-many
        /// </summary>
        private static bool ClassMustBeDeletedBeforeParent(IClassDef childClassDef, ClassDef potentialParentClassDef)
        {
            foreach (IRelationshipDef relationshipDef in potentialParentClassDef.RelationshipDefCol)
            {
                if (relationshipDef.RelatedObjectClassName + relationshipDef.RelatedObjectAssemblyName
                    == childClassDef.ClassName + childClassDef.AssemblyName
                    && relationshipDef is MultipleRelationshipDef)
                {
                    return true;
                }
            }
            return false;
        }

        public static void TestFixtureSetup()
        {
            ClassDef.ClassDefs.Clear();
            ClassDef.LoadClassDefs(new XmlClassDefsLoader(BOBroker.GetClassDefsXml(), new DtdLoader()));

        }

        public static void TestSetup()
        {
            BORegistry.DataAccessor = new DataAccessorInMemory();
            ClearAllTables();
            BusinessObjectManager.Instance.ClearLoadedObjects();
            WaitForGC();
        }
    }
}